package com.boot.schedule;

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.scheduling.support.CronTrigger;

import java.time.Instant;
import java.time.LocalDateTime;

/**
 * @author 霜寒 <1621856595@qq.com>
 * @description 调度任务的简单封装
 * @date 2019/12/30 15:37
 **/
@Slf4j
public abstract class AbstractScheduleTask implements Runnable, ApplicationEventPublisherAware {


    private ApplicationEventPublisher publisher;

    /**
     * @description 任务唯一id
     * @date 2020/2/24 21:23
     **/
    private final String id;

    /**
     * @description 最大重试次数
     * @date 2020/1/9 16:35
     **/
    private final int retryTimes;
    /**
     * @description 任务最大执行时间
     * @date 2020/1/9 16:36
     **/
    private final int maxTime;

    public AbstractScheduleTask(int retryTimes, int maxTime) {
        id = IdUtil.fastSimpleUUID();
        this.retryTimes = retryTimes;
        this.maxTime = maxTime;
    }

    public AbstractScheduleTask(int maxTime) {
        this(3, maxTime);
    }

    public AbstractScheduleTask() {
        this(30000);
    }

    public String getId() {
        return id;
    }

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description Cron表达式
     * @date 2020/1/9 16:39
     **/
    public abstract CronTrigger getCronTrigger();

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description 类似生命周期钩子，在任务实际开始执行前执行此方法，只执行一次
     * @date 2020/1/9 16:39
     **/
    public void beforeStart() {
        log.debug("beforeStart: {}", Instant.now().toString());
    }

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description 开始
     * @date 2020/1/9 16:41
     **/
    public void start() {
        beforeStart();
        publisher.publishEvent(new TaskEvent(this, TaskEvent.START));
    }


    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description 类似生命周期钩子，在业务实际开始执行前执行此方法，执行多次
     * @date 2020/1/9 16:39
     **/
    public void beforeService() {
        log.debug("beforeService: {}", Instant.now().toString());
    }

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description 在实际业务处理
     * @date 2020/1/9 16:39
     **/
    public void service() {

    }

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description 类似生命周期钩子，在业务结束后执行此方法，执行多次
     * @date 2020/1/9 16:40
     **/
    public void afterService() {
        log.debug("afterService: {}", Instant.now().toString());
    }

    public void handleException(Exception e) {
        if (e instanceof RetryFailedException) {
            this.stop();
        } else {
            log.warn("handleException: {}", e.getMessage());
        }
    }

    @Override
    public void run() {
        TaskHolder holder = new TaskHolder(this.retryTimes);
        beforeService();
        try {
            while (holder.retry()) {
                try {
                    service();
                    break;
                } catch (Exception e) {
                    handleException(e);
                }
                if (holder.retryTimes != 0) {
                    log.warn("service执行失败，开始重试");
                }
            }
            afterService();
        } catch (RetryFailedException e) {
            this.stop();
        }
    }

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description 停止
     * @date 2020/1/9 16:41
     **/
    public void stop() {
        publisher.publishEvent(new TaskEvent(this, TaskEvent.STOP));
    }

    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description 类似生命周期钩子，在任务结束后执行此方法，只执行一次
     * @date 2020/1/9 16:40
     **/
    public void afterStop() {
        log.debug("afterStop: {}", Instant.now().toString());
    }


    /**
     * @author 霜寒 <1621856595@qq.com>
     * @description 判断是否超时
     * @date 2020/1/9 16:43
     **/
    protected boolean isTimeOut(LocalDateTime startTime) {
        LocalDateTime deadline = startTime.plusSeconds(maxTime / 1000);
        LocalDateTime now = LocalDateTime.now();
        return now.isAfter(deadline);
    }

    protected CronTrigger everySeconds(int seconds) {
        return new CronTrigger(String.format("0/%d * * * * ? ", seconds));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    public static class TaskHolder {

        private int retryTimes;

        public TaskHolder(int retryTimes) {
            this.retryTimes = retryTimes;
        }

        /**
         * @author 霜寒 <1621856595@qq.com>
         * @description 每调用一次该方法，减少重试次数一次，当次数为 0 时，抛出异常
         * @date 2020/1/9 16:41
         **/
        public boolean retry() {
            if (retryTimes == 0) {
                throw new RetryFailedException();
            } else {
                retryTimes--;
                return true;
            }
        }
    }
}
